import { getBrowser } from '@/util/index'
import { uuid } from '@web-utils/integrations/uuid'
import { sendMessage } from '@/components/im/api/message'
import { getToken } from '@/util/auth'

const APP_VERSION = '1.0.0'
const APP_CHANNEL = 'web'
const APP_PACKAGE = 'com.fuyao.cim'

const getURI = () =>
  `ws://${window.location.host}${process.env.VUE_APP_PUBLIC_PATH}cloud-im-ws?access_token=${getToken()}&TENANT-ID=1`
// const getURI = () => `ws://localhost:34567/cloud-im-ws?access_token=${getToken()}&TENANT-ID=1`

/*
 * 特殊的消息类型，代表被服务端强制下线
 */
const ACTION_999 = '999'
const MESSAGE = 2
const REPLY_BODY = 4
const SENT_BODY = 3
const PING = 1
const PONG = 0
/*
 * 握手鉴权常量
 */
const KEY_HANDSHAKE = 'client_handshake'
const CODE_UNAUTHORIZED = '401'
const KEY_CLIENT_BIND = 'client_bind'
/**
 * PONG 字符串转换后
 * @type {string}
 */
const PONG_BODY = 'PONG'

function getDeviceId() {
  const deviceId = window.localStorage.deviceId || uuid()
  window.localStorage.deviceId = deviceId
  return deviceId
}

export class CIMPushManager {
  /**
   * @param options {{ account, onOpen, onClose, onError, onMessage }}
   */
  constructor(options = {}) {
    const { account, onOpen, onClose, onError, onMessage } = options
    window.localStorage.deviceId = ''
    this.deviceId = getDeviceId()
    this.socket = null
    this.manualStop = false
    this.account = account
    this.events = {}
    this.listeners = { onOpen, onClose, onError, onMessage }
  }

  setAccount(account) {
    if (this.account !== account || this.socket?.readyState === 3) {
      this.account = account
      this.connect()
    }
  }

  addMessageListener(action, listener) {
    const typeEvents = this.events[action]
    if (typeEvents) {
      typeEvents.add(listener)
    } else {
      this.events[action] = new Set()
      this.events[action].add(listener)
    }
  }

  removeMessageListener(action, listener) {
    const typeEvents = this.events[action]
    if (typeEvents) {
      typeEvents.delete(listener)
      if (typeEvents.size === 0) {
        delete this.events[action]
      }
    }
  }

  clearMessageListener() {
    for (const eventsKey in this.events) {
      delete this.events[eventsKey]
    }
  }

  connect() {
    if (!this.account) {
      return
    }
    this.manualStop = false
    this.socket = null
    this.socket = new WebSocket(getURI())
    this.socket.cookieEnabled = false
    this.socket.onopen = event => {
      this.bind()
      this.listeners.onOpen?.(event)
    }
    this.socket.onmessage = async event => {
      await this.onMessage(event)
      this.listeners.onMessage?.(event)
    }
    this.socket.onclose = event => {
      this.onClose()
      this.listeners.onClose?.(event)
    }
    this.socket.onerror = event => {
      this.listeners.onError?.(event)
    }
  }

  bind() {
    const browser = getBrowser()
    const body = {}
    body.key = KEY_CLIENT_BIND
    body.timestamp = new Date().getTime()
    body.data = {}
    body.data.uid = this.account
    body.data.channel = APP_CHANNEL
    body.data.appVersion = APP_VERSION
    body.data.osVersion = browser.version
    body.data.deviceName = browser.name
    body.data.packageName = APP_PACKAGE
    body.data.deviceId = this.deviceId
    body.data.language = navigator.language
    this.sendRequest(body)
  }

  sendRequest(body) {
    const data = {}
    data.type = SENT_BODY
    data.content = JSON.stringify(body)
    this.socket.send(JSON.stringify(data))
  }

  sendMessage({ content, receiver }) {
    return sendMessage({ content, receiver })
  }

  stop() {
    this.manualStop = true
    this.socket.close()
  }

  resume() {
    this.manualStop = false
    this.connect()
  }

  pong() {
    const data = {}
    data.type = PONG
    data.content = PONG_BODY
    this.socket.send(JSON.stringify(data))
  }

  onClose() {
    if (!this.manualStop) {
      const time = Math.floor(Math.random() * (30 - 15 + 1) + 15)
      setTimeout(() => this.connect(), time)
    }
  }

  async onMessage(e) {
    const data = JSON.parse(e.data)
    const type = data.type
    const body = data.content

    if (type === PING) {
      this.pong()
      return
    }

    if (type === MESSAGE) {
      const message = JSON.parse(body)
      const action = message.action
      // 被强制下线之后，不再继续连接服务端
      if (action === ACTION_999) {
        this.manualStop = true
      }

      try {
        if (action && this.events[action]) {
          if (action === 'chat') {
            const rawMessage = JSON.parse(data.content)
            data.content = rawMessage
            rawMessage.content = JSON.parse(rawMessage.content)
          }
          this.events[action].forEach(func => func && func?.(data))
        }
      } catch (e) {
        // console.log(e)
      }
      return
    }

    if (type === REPLY_BODY) {
      const reply = JSON.parse(body)
      // 判断是否是握手鉴权失败,终止后续自动重连
      if (reply.key === KEY_HANDSHAKE && reply.code === CODE_UNAUTHORIZED) {
        this.manualStop = true
      }
    }
  }
}

export const cimPushManager = new CIMPushManager()
